A checkout endpoint that would hand over anyone's invoice
One object identifier, no ownership check, and a chain that reached the billing records of every customer on the platform.
Where it started
The engagement covered a retail platform: web checkout, a mobile app and the API behind both. Scope listed 214 external assets. Recon turned up 231, and the extra seventeen were the interesting ones. Among them was a versioned API host that did not appear in the client’s own inventory and had no WAF in front of it.
The mobile app talked to that host. Pulling the app bundle and watching traffic through a proxy gave us the full route list in under an hour, including several endpoints the web client never calls.
The finding
Invoice retrieval took an order identifier in the path. The handler checked that the caller held a valid session. It never checked that the session owned the order.
Identifiers were sequential. A single authenticated account could walk the range and pull every invoice the platform had issued. We stopped at eleven records, all belonging to test accounts we had created, and captured the evidence there.
Why it scored critical
On its own, an authorisation gap on one route is serious. What moved it to 9.1 was what it unlocked. Invoices carried the billing email. The password reset flow accepted that email and, on this platform, a reset did not invalidate active sessions. Read access to invoices became a path to account takeover without touching the login page.
Scanners flagged none of this. Every request was well-formed, authenticated and returned 200.
The fix
The client shipped an ownership check on the handler within 48 hours and moved to opaque identifiers in the following sprint. We re-tested both changes. The second one matters more: sequential identifiers make every future authorisation slip enumerable.
Details in this post are redacted and figures are representative. Published with the client’s consent after remediation was verified.